/* SPDX-License-Identifier: GPL-2.0 */
/*
 * VPMEM Metadata definition
 *
 * Copyright (C) 2020 HUAWEI, Inc.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#ifndef _METADATA_H
#define _METADATA_H

#include <linux/types.h>
#include <linux/seq_file.h>
#include <linux/mempolicy.h>

#include <linux/vpmem_numa.h>

#include <linux/types.h>
#include <linux/ioctl.h>

#define VPMEM_API_VERSION        1

#define VPMEM_NAME_SIZE  15
#define VPMEM_UUID_SIZE  16
#define VPMEM_NO_NUMA    0xff

#define VPMEM_TYPE       ('v')
#define VPMEM_BASE       100

#define VPMEM_MAX_PATH (256)

struct vpmem_block_info {
	char name[VPMEM_NAME_SIZE];
	__u8 numa;
	__u8 uuid[VPMEM_UUID_SIZE];
	__u64 size;
};

#define VPMEM_CREATE_BLOCK		_IOR(VPMEM_TYPE, VPMEM_BASE + 1, struct vpmem_block_info)

#define VPMEM_PERSISTEN_BLK		_IO(VPMEM_TYPE, VPMEM_BASE + 2)

#define VPMEM_BLOCK_IS_PERSIST	_IOR(VPMEM_TYPE, VPMEM_BASE + 3, struct vpmem_block_info)

#define VPMEM_BLOCK_IS_INUSE	_IOR(VPMEM_TYPE, VPMEM_BASE + 4, struct vpmem_block_info)

#define VPMEM_UNPERSISTEN_BLK	_IO(VPMEM_TYPE, VPMEM_BASE + 5)

#define VPMEM_BLOCK_IS_EXIST	_IOR(VPMEM_TYPE, VPMEM_BASE + 6, struct vpmem_block_info)

#define VPMEM_RELEASE_BLOCK		_IO(VPMEM_TYPE, VPMEM_BASE + 7)

#define VPMEM_PAGE_SHIFT	12
#define VPMEM_PAGE_SIZE	(_AC(1, UL) << VPMEM_PAGE_SHIFT)
#define VPMEM_PAGE_MASK	(~(VPMEM_PAGE_SIZE-1))

#define VPMEM_PUD_SHIFT	21
#define VPMEM_PUD_SIZE	(_AC(1, UL) << VPMEM_PUD_SHIFT)
#define VPMEM_PUD_MASK	(~(VPMEM_PUD_SIZE-1))

#define VPMEM_HUGE_PAGE_SHIFT	(VPMEM_PUD_SHIFT)

#define METADATA_SIZE			(VPMEM_PUD_SIZE) //2M
#define METADATA_UNIT_SIZE		(sizeof(struct metadata_unit))
#define METADATA_UNIT_COUNT		((METADATA_SIZE - sizeof(struct metadata_header)) \
								 / METADATA_UNIT_SIZE)
#define METADATA_MAGIC			"HW-VPMEM"

#define RESERVED_SIZE			((VPMEM_PUD_SIZE) * 8) //16M

#define TASK_NAME ((current && current->comm) ? (current->comm) : "<unknown>")
#define TASK_PID (task_pid_nr(current))
#define TITLE_FMT "vpmem: %s[%d]: "

#define VPMEM_INFO(fmt, arg...) pr_info(TITLE_FMT fmt, TASK_NAME, TASK_PID, ## arg)
#define VPMEM_WARN(fmt, arg...) pr_warn(TITLE_FMT fmt, TASK_NAME, TASK_PID, ## arg)
#define VPMEM_ERR(fmt, arg...) pr_err(TITLE_FMT fmt, TASK_NAME, TASK_PID, ## arg)

#define strstarts(s, prefix)	(strncmp((s), (prefix), strlen(prefix)) == 0)

void vpage_info(struct seq_file *m);

struct metadata_block {
	struct list_head entry;
	struct list_head ranges;
	u8 uuid[VPMEM_UUID_SIZE];
	char name[VPMEM_NAME_SIZE];
	u8 flags;
};

struct metadata_range {
	/* Double linked in metadata_block.ranges or
	 * metadata_header.free_pool
	 */
	struct list_head entry;
	/* Double linked in metadata_header.used_ranges*/
	struct list_head used;
	struct metadata_block *parent;
	u64 start;
	u64 size;
	u8 numa;
	u8 pad[7];
};

struct metadata_unit {
	struct list_head entry;
	uint64_t pad[6];
};

struct metadata_header {
	char magic[8];
	u64 base_addr;
	/* Metadata header version */
	u32 version;
	/* Not used metadata unit number */
	u32 free_unit;
	/* The VPMEM physical address start position */
	u64 start;
	/* Reserved memory size for VPMEM */
	u64 size;
	/* Not used metadata unit double link list, alloc
	 * metadata_range or metadata_block from this list.
	 */
	struct list_head free_units;
	/* Not used memory ranges double link list. */
	struct list_head free_pool;
	struct list_head used_blocks;
	struct list_head used_ranges;
	u64 pad[19];
	struct metadata_unit units[0];
};

typedef struct metadata_header __iomem *metadata_header_ptr;
typedef struct metadata_unit __iomem *metadata_unit_ptr;
typedef struct metadata_block __iomem *metadata_block_ptr;
typedef struct metadata_range __iomem *metadata_range_ptr;

struct vpmem {
	struct mutex lock;
	struct list_head vpmem_blk_header;
	metadata_header_ptr metadata_header;
	struct vpmem_stat stat;
	struct proc_dir_entry *info_entry;
	struct proc_dir_entry *state_entry;
	struct proc_dir_entry *hugepages_entry;
	struct proc_dir_entry *cmdline;
	struct mempolicy pols[MAX_NUMNODES];
};

enum {
	blk_persisten = 7,
};
#define VPMEM_BLOCK_PERSISTEN	(1 << blk_persisten)
#define VPMEM_BLOCK_FLAG_MASK	(0xF)

#define VPMEM_BLOCK_IS_PERSISTEN(blk) (blk->flags & VPMEM_BLOCK_PERSISTEN)
#define VPMEM_BLOCK_SET_PERSISTEN(blk) (blk->flags |= VPMEM_BLOCK_PERSISTEN)
#define VPMEM_BLOCK_CLR_PERSISTEN(blk) (blk->flags &= (~VPMEM_BLOCK_PERSISTEN))

struct vpmem_blk_struct {
	metadata_block_ptr blk;
	char __iomem *vaddr;
	u64 start;
	u64 end;
	struct list_head entry;
};

metadata_header_ptr metadata_init(u8 numa, u64 start, u64 size, bool force,
		bool *reused);

void metadata_fini(metadata_header_ptr header);

int metadata_add_range(metadata_header_ptr header, u64 start, u64 size, u8 numa);

void metadata_info(metadata_header_ptr header, struct seq_file *m);

void metadata_info_timestamp(metadata_header_ptr header, struct seq_file *m);

void metadata_ranges_stat(struct list_head *ranges, u64 *stat, u32 len, bool add);

void metadata_info_hugepages(metadata_header_ptr header, struct vpmem_stat *stat);

int metadata_add_timestamp(metadata_header_ptr header, const char *name);

metadata_block_ptr metadata_block_alloc(metadata_header_ptr header,
		struct vpmem_block_info *info);

metadata_block_ptr metadata_block_find(metadata_header_ptr header,
		struct vpmem_block_info *info);

void metadata_block_free(metadata_header_ptr header,
		metadata_block_ptr blk);

long metadata_block_truncate(metadata_header_ptr header,
		metadata_block_ptr blk, u8 numa, size_t size);

u64 metadata_block_size(metadata_block_ptr blk);

u64 metadata_ranges_size(struct list_head *ranges);

void *metadata_remap_ranges(struct list_head *ranges, u64 start);

void metadata_unmap_ranges(struct list_head *ranges, u64 start);

struct vpmem *vpmem_info_get_locked(void);

void vpmem_info_put_unlocked(void);

metadata_block_ptr vpmem_blk_alloc(struct vpmem *p, struct vpmem_block_info *info);
int vpmem_blk_is_opened(struct vpmem *p, metadata_block_ptr blk);

static inline bool metadata_block_is_vm_ram(const char *name)
{
	return ((strstarts(name, "pc.ram")) || (strstarts(name, "ram-node")) ||
		(strstarts(name, "mach-virt.ram")));
}

#endif /* _METADATA_H */
